﻿using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.ViewComponents;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using PlutoStudio.AspNetCore.EmbeddedResources;
using PlutoStudio.AspNetCore.Mvc.Extensions;
using PlutoStudio.AspNetCore.Mvc.Providers;
using PlutoStudio.AspNetCore.Mvc;
using PlutoStudio.Dependency;
using PlutoStudio.Modules;
using System;
using System.Collections.Generic;
using System.Text;
using Autofac;
using Module = PlutoStudio.Modules.Module;
using Autofac.Extensions.DependencyInjection;

namespace PlutoStudio.AspNetCore
{
    public static class ServiceCollectionExtensions
    {
        /// <summary>
        /// Integrates ABP to AspNet Core.
        /// </summary>
        /// <typeparam name="TStartupModule">Startup module of the application which depends on other used modules. Should be derived from <see cref="AbpModule"/>.</typeparam>
        /// <param name="services">Services.</param>
        public static IServiceProvider AddAbp<TStartupModule>(this IServiceCollection services)
            where TStartupModule : Module
        {
            return services.AddPlutoStudio<TStartupModule>(options => { });
        }

        /// <summary>
        /// Integrates ABP to AspNet Core.
        /// </summary>
        /// <typeparam name="TStartupModule">Startup module of the application which depends on other used modules. Should be derived from <see cref="AbpModule"/>.</typeparam>
        /// <param name="services">Services.</param>
        /// <param name="optionsAction">An action to get/modify options</param>
        public static IServiceProvider AddPlutoStudio<TStartupModule>(this IServiceCollection services, Action<ServiceOptions> optionsAction)
            where TStartupModule : Modules.Module
        {
            var bootstrapper = AddBootstrapper<TStartupModule>(services, IocManager.Instance);
            var options = new ServiceOptions
            {
                IocManager = IocManager.Instance,
                Bootstrapper=bootstrapper
            };

            optionsAction(options);

            ConfigureAspNetCore(services, options.IocManager);

            //      abpBootstrapper.PlugInSources.AddRange(options.PlugInSources);

            return services.AddAutofac(bootstrapper.IocManager.IocContainer);
        }

        private static void ConfigureAspNetCore(IServiceCollection services, IIocResolver iocResolver)
        {
            //See https://github.com/aspnet/Mvc/issues/3936 to know why we added these services.
            services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
            services.TryAddSingleton<IActionContextAccessor, ActionContextAccessor>();

            //Use DI to create controllers
            services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>());

            //Use DI to create view components
            services.Replace(ServiceDescriptor.Singleton<IViewComponentActivator, ServiceBasedViewComponentActivator>());

            //Change anti forgery filters (to work proper with non-browser clients)
            services.Replace(ServiceDescriptor.Transient<AutoValidateAntiforgeryTokenAuthorizationFilter, AutoValidateAntiforgeryTokenAuthorizationFilter>());
            services.Replace(ServiceDescriptor.Transient<ValidateAntiforgeryTokenAuthorizationFilter, ValidateAntiforgeryTokenAuthorizationFilter>());

            //Add feature providers
            var partManager = services.GetSingletonServiceOrNull<ApplicationPartManager>();
            partManager.FeatureProviders.Add(new AppServiceControllerFeatureProvider(iocResolver));

            //Configure JSON serializer
            //services.Configure<MvcJsonOptions>(jsonOptions =>
            //{
            //    jsonOptions.SerializerSettings.Converters.Insert(0, new DateTimeConverter());
            //});

            //Configure MVC
            services.Configure<MvcOptions>(mvcOptions =>
            {
                mvcOptions.AddPlutoStudio(services);
            });

            //Configure Razor
            services.Insert(0,
                ServiceDescriptor.Singleton<IConfigureOptions<RazorViewEngineOptions>>(
                    new ConfigureOptions<RazorViewEngineOptions>(
                        (options) =>
                        {
                            options.FileProviders.Add(new EmbeddedResourceViewFileProvider(iocResolver));
                        }
                    )
                )
            );
        }

        private static Bootstrapper AddBootstrapper<TStartupModule>(IServiceCollection services, IIocManager iocManager)
            where TStartupModule : Modules.Module
        {
            var abpBootstrapper = Bootstrapper.Create<TStartupModule>(iocManager);
            services.AddSingleton(abpBootstrapper);
            return abpBootstrapper;
        }

        /// <summary>
        /// Adds the <see cref="AutofacServiceProviderFactory"/> to the service collection.
        /// </summary>
        /// <param name="services">The service collection to add the factory to.</param>
        /// <param name="configurationAction">Action on a <see cref="ContainerBuilder"/> that adds component registrations to the container.</param>
        /// <returns>The service collection.</returns>
        public static IServiceProvider AddAutofac(this IServiceCollection services,IContainer container)
        {
            var builder = new ContainerBuilder();
            builder.Populate(services);
            builder.Update(container);
            return new AutofacServiceProvider(container);
        }
    }
}
